package
{
	import dyn.DTiler;
	import enemies.Mine;
	import enemies.Turret;
	import ex.Cap;
	import ex.Circle;
	import flash.filters.BlurFilter;
	import flash.filters.DropShadowFilter;
	import flash.utils.getTimer;
	import guns.*;
	import flash.display.*;
	import flash.events.*;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.system.FSCommand;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFieldAutoSize;
	
	
	import ex.Rectangle;
	import ex.Map;
	import enemies.*;
	import flash.utils.Dictionary;
	import flash.net.SharedObject;
	import emb.SoundLib;
	/**
	 * ...
	 * @author Daria Savkova
	 */
	
	public class Main extends Sprite
	{
		private var hp:Sprite;
		private var armor:Sprite;
		private var time:TextField;
		private var gameTime:int;
		private var gameStartTime:int;
		
		private var lastFrameTime:int;
		private var lastSimulationTime:int;
		private const simulationPeriod:int = 40;//1/25 of second
		private var frameTime;
		public var simulationTimes:uint;
		
		
		private var frags:TextField;
		private var progress:int;
		private var loadtext:TextField;
		private const NUMBEROFLEVELS = 1;
		public var p:Player;
		private var fs:Vector.<MovingCircle>;
		private static var instance:Main;
		private var map:ex.Map;
		private var zoneDict:Dictionary;
		private var hudformatter:TextFormat;
		public var container:Sprite;
		//Like registers to reduce malloc
		private var circle_ax:Circle;
		private var circle_bx:Circle;
		private var circle_cx:Circle;
		private var enemycount:int;
		
		private var lso:SharedObject;
		private var enemiesKilled:int;
		
		private var elapsedTimer:Timer;//Second interval
		
		public var gamePaused:Boolean;
		private var gamePauseTime:uint;
		private var cap:Cap;
		
		
		private var tiler:DTiler;
		
		private var smokes:Vector.<Smoke>;
		
		
		public static function Instance():Main
		{
			if (!instance)
				instance = new Main();
			return instance;
		}
		public function AddCollidingChild(o:MovingCircle):void
		{
			fs.push(o);
			container.addChild(o);
		}
		
		public function Main()
		{
			smokes = new Vector.<Smoke>();
			simulationTimes = 0;
			cap = new Cap();
			gamePaused = false;
			lso = SharedObject.getLocal("tanx");
			if (lso.data.kills)
			{
				enemiesKilled = lso.data.kills;
			}
			else
			{
				enemiesKilled = 0;//First play !?
			}
			
			lastFrameTime = 0;
			lastSimulationTime = 0;
			container = new Sprite();
			var q:Array;
			circle_ax = new Circle(0, 0, 0);
			circle_bx = new Circle(0, 0, 0);
			circle_cx = new Circle(0, 0, 0);
			loadtext = new TextField();
			loadtext.text = "Please wait, loading";
			addChild(loadtext);
			addEventListener(Event.ENTER_FRAME, loading);

			progress = 0;
			instance = this;
			
		}
		
		
		public function loading(event:Event):void
		{
			//if (!progress)
			{
				if (progress == 1)
				{
					loadtext.text = "Loading tiles...";
					tiler = new DTiler();
					if (!tiler) throw "Bad tiler";
					progress++;
				}
				if (progress == 2)
				{
					if (tiler.done)
					{
						loadtext.text = "Loading map...";
						progress++;
					}
					if (tiler.error)
					{
						loadtext.text = "Error while loading tiles...";
						tiler = null;
						//progress++;
					}
				}
				else if (progress == 3)
				{
					map = new Map(tiler);
					LoadLevel();
					stage.focus;
					hud();
					removeEventListener(Event.ENTER_FRAME, loading);
					addEventListener(Event.ENTER_FRAME, moveActors);
					removeChild(loadtext);
				}
				else
				{
					loadtext.text = "Loading " + progress;
					progress ++;
				}
				
				
				
				
			}
		}
		
		public function hud():void
		{
			hudformatter = new TextFormat();
			hudformatter.bold = true;
			hudformatter.size = 20;
			hp = new Sprite();
			armor = new Sprite();
			time = new TextField();
			frags = new TextField();
			hp.graphics.beginFill(0x00FF00, 0.8);
			hp.graphics.drawRect(10, 10, p.hitpoints*100/p.MAXHP, 9);
			hp.graphics.endFill();
			armor.graphics.beginFill(0x0000FF, 0.8);
			armor.graphics.drawRect(10, 20, p.armor*100/p.MAXARMOR, 9);
			armor.graphics.endFill();
			time.x = 10;
			time.y = 30;
			time.textColor = 0xFFFFFF;
			frags.textColor = 0xFFFFFF;
			frags.x = 10;
			frags.y = 60;
			addChild(hp);
			addChild(armor);
			time.autoSize = TextFieldAutoSize.LEFT;
			time.alpha = 0.8;
			time.setTextFormat(hudformatter);
			frags.autoSize = TextFieldAutoSize.LEFT;
			frags.alpha = 0.8;
			addChild(time);
			addChild(frags);
			updateFrags();
			gameStartTime = flash.utils.getTimer();
			gameTime = 0;
			elapsedTimer = new Timer(1000, 0);
			elapsedTimer.addEventListener(TimerEvent.TIMER, updateTime);
			elapsedTimer.start();
		}
		public function updateHpBar():void
		{
			hp.graphics.beginFill(0xFF0000, 0.8);
			hp.graphics.drawRect(10 + p.hitpoints * 100 / p.MAXHP, 10, 100 - p.hitpoints * 100 / p.MAXHP, 9);
			hp.graphics.beginFill(0x00FF00, 0.8);
			hp.graphics.drawRect(10, 10, p.hitpoints * 100 / p.MAXHP, 10);
			hp.graphics.endFill();
		}
		public function updateArmorBar():void
		{
			armor.graphics.beginFill(0xFF0000, 0.8);
			armor.graphics.drawRect(10 + p.armor * 100 / p.MAXARMOR, 20, 100 - p.armor * 100 / p.MAXARMOR, 9);
			armor.graphics.endFill();
		}
		
		public function updateTime(event:Event):void
		{			
			gameTime = simulationTimes * simulationPeriod;//flash.utils.getTimer() - gameStartTime;
			
			time.text = "Time: " +int(gameTime / (1000 * 60)) + ":" + int(((gameTime / 1000) %60)/10)+int(((gameTime / 1000) % 60)%10);
			time.setTextFormat(hudformatter);
		}
		public function updateFrags():void
		{
			frags.text = "Enemies left / killed: " + enemycount + "/" + enemiesKilled;
			frags.setTextFormat(hudformatter);
		}
		public function LoadLevel():void
		{
			enemycount = 0;
			map.drawon(container.graphics);
			fs = new Vector.<MovingCircle>();
			var f:MovingCircle;
			p = new Player();//Player must exist so turrets have target
			for (var j:int=0; j < map.omap[map.levelnum].width; j++)
			for (var i:int=0; i < map.omap[map.levelnum].height; i++)
			{
				if ((map.omap[map.levelnum].getPixel(j, i)  > 0) && (map.omap[map.levelnum].getPixel(j, i)!=0xFFFFFF))
				{
					var c:uint = map.omap[map.levelnum].getPixel(j, i);
					switch (c)
					{
						case 0xFE0000: 
						case 0xFF0000: //player
							trace("Ima new player!");
							//p = new Player();
							f = p;
							break;
						case 0x0025FE:
						case 0x0026FF: //Moving turret
							trace("Ima moving turret!");
							f=new MovingTurret(stage, p);
							break;
						case 0x4BFE00:
						case 0x4CFF00: //turret
							trace("Ima new turret!");
							f = new Turret(stage, p);
							//if (!p.posesses)
							//	p.Posess(f as Turret);
							break;
						case 0x7E6900:
						case 0x7F6A00: //mine
							trace("Ima new mine!");
							f= new Mine(stage, p);
							break;
						case 0x7E3200:
						case 0x7F3300: //armored moving turret
							trace("Ima new armored moving turret!");
							f = new ArmoredMovingTurret(stage, p);
							/*if (!p.posesses)
								p.Posess(f as ArmoredMovingTurret);
								*/
							break;
						case 0xFE7E7E: 
						case 0xFF7F7F://moving mine
							trace("Ima new moving mine!");
							f= new MovingMine(stage, p);
							break;
						case 0x00FF00: //medikit
							trace("medikit!");
							f = new Medikit(stage, p);
							break;
						default:
							continue;
					}
					f.SetStartPosition((j + 0.5) * map.dim.x, (i + 0.5) * map.dim.y);
					enemycount++;
					fs.push(f);
					container.addChild(f);
					//loading();
				}			
			}
			container.removeChild(p);
			container.addChild(p);
			enemycount--;
			addChild(container);
		}
		public function AreCollidingCircleCircle(x1:Number, y1:Number, r1:Number, x2:Number, y2:Number, r2:Number):Boolean
		{
			return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) < (r1 + r2) * (r1 + r2);
		}
				
		public function Cantor(x:int, y:int)
		{
			return (x + y) * (x + y + 1) / 2 + y;
		}
		
		public function ArrIdx(x:int, y:int, z:int)
		{
			return (z * 1000 + y) * 1000 + x;
		}
		
		public function TimeOfCollide(f:MovingCircle,m:Map):Number
		{
			var t:Number = 1;
			var base:Circle = circle_ax;
			f.GetCircleOld(base,0);
			var now:Circle = circle_bx;
			f.GetCircleOld(now,1);
			now.center.x = base.center.x + f.dx * t;
			now.center.y = base.center.y + f.dy * t;
			if (m.collidesCircle(now,f.MaskCollide))
			{
								var tc:Number = t;
								var tnc:Number = 0;
								t = (tc + tnc) / 2;
								while (tc - t > 0.001)
								{
									now.center.x = base.center.x + f.dx * t;
									now.center.y = base.center.y + f.dy * t;
									if (m.collidesCircle(now,f.MaskCollide))
									{
										tc = t;
									}
									else
									{
										tnc = t;
									}
									t = (tc + tnc) / 2;
								}
								t = tnc;
								if (t <= 0.001)
								{
									t = 0;
									return t;
								}
							}
							return t;
		}
		
		public function MineSweep():void
		{
			if (p.posesses) return;
			for (var i:int = 0; i < fs.length; i++)
			{
				var t:Turret = fs[i] as Turret;
				var m:Mine = fs[i] as Mine;
				if (m)
				{
				if (Math.pow(p.x - m.x, 2) + Math.pow(p.y - m.y, 2) <= Math.pow(200, 2)) m.Detect();
				}
				else if (t)
				{
					if (t.brainDead && p.wantEnter) //!p.posesses already checked
					{
						if ((Math.pow(p.x - t.x, 2) + Math.pow(p.y - t.y, 2) <= Math.pow(t.r, 2)))
						{
							p.Posess(t);
							return;//No more sweep
						}
					}
				}
			}
		}
		
		
		public function StopColliding3():void
		{
			MineSweep();
		//	var fslength:int = fs.length;
			var gridStep:int = 25;
			zoneDict = new Dictionary(true);
			//Fill zone with objects at t=0
			for (var ii:int = 0; ii < fs.length; ii++)
			{
				var cc:MovingCircle = fs[ii];
				cc.sa.SyncSane();
				cc.sb.x = cc.sa.x + cc.dx;
				cc.sb.y = cc.sa.y + cc.dy;
				//cc.sb.SyncSane();
				var ib:int = Math.floor(cc.left / gridStep);
				var ie:int = Math.floor(cc.right / gridStep);
				var jb:int = Math.floor(cc.top / gridStep);
				var je:int = Math.floor(cc.bottom / gridStep);
				for (var j:int = jb; j <= je; j++)
					for (var i:int = ib; i <= ie; i++)
					{
						var k:int = 0;
						for (; zoneDict[ArrIdx(i, j, k)]; k++)
						{
						}
						;
						zoneDict[ArrIdx(i, j, k)] = cc;
					}
			}
			
			//Now check how an object can move
			
			
			for (var ii:int = 0; ii < fs.length; ii++)
			{
				var fsii:MovingCircle = fs[ii];
				var t:Number = TimeOfCollide(fsii, map);
				var collidesWall:Boolean = (t != 1);
				var base:Circle = circle_ax;
				fsii.GetCircleOld(base,0);
				var now:Circle = circle_bx;
				fsii.GetCircleOld(now,t);
				
				
				var cr:flash.geom.Rectangle = base.bounds().union(now.bounds());
				cr = cr.union(now.bounds());
				var ib:int = Math.floor(cr.left / gridStep);
				var ie:int = Math.floor(cr.right / gridStep);
				var jb:int = Math.floor(cr.top / gridStep);
				var je:int = Math.floor(cr.bottom / gridStep);
				for (var j:int = jb; j <= je && t > 0.001; j++)
				{
					for (var i:int = ib; i <= ie && t > 0.001; i++)
					{
						var k:int = 0;
						for (; zoneDict[ArrIdx(i, j, k)]; k++)
						{
							var fol: MovingCircle = zoneDict[ArrIdx(i, j, k)] as MovingCircle;
							
							var crc:Circle = circle_cx;
							fol.GetCircleOld(crc,0);
							//Self-intersection is not valid
							if (zoneDict[ArrIdx(i, j, k)] === fsii)
							{
								//Non-intersectable
								continue;
							}
							if (now.intersects(crc))
							{
								fsii.Interact(fol);
							}
							if (!fsii.CollideableWith(fol)) continue;
							if (now.intersects(crc))
							{
								collidesWall = false;
								var tc:Number = t;
								var tnc:Number = 0;
								t = (tc + tnc) / 2;
								while (tc - t > 0.001)
								{
									now.center.x = base.center.x + fsii.dx * t;
									now.center.y = base.center.y + fsii.dy * t;
									if (now.intersects(crc))
									{
										tc = t;
									}
									else
									{
										tnc = t;
									}
									t = (tc + tnc) / 2;
								}
								if (t <= 0.001)
								{
									t = 0;
									break;
								}
							}
						}
					}
				}
				if (t <= 0.001)
					t = 0;
				fsii.x += fsii.dx * t;
				fsii.y += fsii.dy * t;
				//TO DO: Make it through interaction
				if (fsii as Bullet && collidesWall)
				{
					fsii.removeMe = true;
				}
				
				if (collidesWall && fsii.dx != 0 && fsii.dy != 0)
				{
					//It's a dirty hack
					var leftDx:Number = fsii.dx * (1 - t);
					var leftDy:Number = fsii.dy * (1 - t);
					//Maybe we bumped horisontally
					fsii.dx = 0;
					fsii.dy = leftDy;
					var tColVert:Number = TimeOfCollide(fsii, map);
					//Maybe vertically
					fsii.dx = leftDx;
					fsii.dy = 0;
					var tColHoriz:Number = TimeOfCollide(fsii, map);
					if (tColVert > tColHoriz)
					{
						fsii.dx = 0;
					    fsii.dy = leftDy;
					}

						ii--;
						continue;
						
				}
				fs[ii].sb.SyncSane();					
			}
			//Clear up bullets
			//TO DO: Use fs.filter
			for (var i:int = 0; i < fs.length; i++)
			{
 				while (i < fs.length && fs[i].removeMe)
				{
					container.removeChild(fs[i]);
					fs[i] = fs[0];
					fs.shift();
				}
			}
			
		}
		
		public function Puff(x_:Number, y_:Number):void
		{
			for (var i:int = 0; i < smokes.length; i++)
			{
				if (smokes[i].IsUsedUp())
				{
					smokes[i].x = x_;
					smokes[i].y = y_;
					smokes[i].startsim = simulationTimes;
					return;
				}
			}
			var s:Smoke = new Smoke();
			s.x = x_;
			s.y = y_;
			s.startsim = simulationTimes;
			smokes.push(s);
			container.addChild(s);
		}
		
		public function moveActors(event:Event):void
		{
			if (gamePaused) return;
			var simulated:Boolean = false;
			frameTime = getTimer();
			
			if (lastSimulationTime + simulationPeriod < frameTime)
			{
				for (var i:int = 0 ; i < fs.length ; i++) 
				{
					fs[i].x = fs[i].sb.x;
					fs[i].y = fs[i].sb.y;
					
					fs[i].movePlayer(null);
				}
				StopColliding3();//Not fast to call each frame
				lastSimulationTime = frameTime; 
				simulated = true;
				//trace("Simulated!");
			}
			
			{
				var t:Number = (frameTime - lastSimulationTime) / simulationPeriod;
				for (var j:int = 0; j < smokes.length; j++)
				{
					smokes[j].DrawAtT(t);
				}
				
				for (var i:int = 0 ; i < fs.length ; i++) 
				{
					fs[i].SaneStates(); 
					fs[i].sa.ApplyMix(fs[i].sb, t);
				}
			}
			if (p.posesses)
			{
				p.x = p.posesses.x;
				p.y = p.posesses.y;
			}
			
			//Forgot why do I need lastFrameTime... FPS count?
			lastFrameTime = frameTime;
			scrollLevel();
			if (simulated)
			{
				lastSimulationTime = getTimer();
				simulationTimes++;
			}
		}
		
		public function Pause():void
		{
			if (gamePaused) return;
			this.addChild(cap);
			gamePaused = true;
			gamePauseTime = lastFrameTime;
		}
		
		public function UnPause():void
		{
			var t:uint = getTimer();
			lastSimulationTime = lastSimulationTime + t - gamePauseTime;
			this.removeChild(cap);
			gamePaused = false;
			
		}
		
		public function PauseKey():void 
		{
			if (gamePaused) UnPause();
			else Pause();
		}
		
		public function Piggy():Number
		{
			var x1:Number;
			var x2:Number;
			var a:MovingCircle = fs[0];
			var b:MovingCircle = fs[1];
			var f:Array = Polynomial.Sub(Polynomial.Add(Polynomial.Sqr(Polynomial.Sub(new Array(a.x, a.dx), new Array(b.x, b.dx))), Polynomial.Sqr(Polynomial.Sub(new Array(a.y, a.dy), new Array(b.y, b.dy)))), Polynomial.Sqr(Polynomial.Add(new Array(a.r, 0), new Array(b.r, 0))));
			quadSolve(f[2], f[1], f[0], x1, x2);
			if ((x1 != Number.NaN && x2 != Number.NaN) && (x1 < x2))
				return x1;
			if ((x1 != Number.NaN && x2 != Number.NaN) && (x2 < x1))
				return x2;
			if (x1 != Number.NaN)
				return x1;
			return Number.NaN;
		}
		
		public function linearSolve(a:Number, b:Number, x:Number):void
		{
			x = (-a) / b;
		}
		
		//TO DO : Fixme for linear equation
		public function quadSolve(a:Number, b:Number, c:Number, x1:Number, x2:Number):void
		{
			var d:Number = new Number();
			d = b * b - 4 * a * c;
			if (d == 0)
			{
				x1 = -b / (2 * a);
				x2 = Number.NaN;
				return;
			}
			if (d < 0)
			{
				x1 = Number.NaN;
				x2 = Number.NaN;
				return;
			}
			if (d > 0)
			{
				x1 = -b + Math.sqrt(d) / (2 * a);
				x2 = -b - Math.sqrt(d) / (2 * a);
				return;
			}
		}
		
		public function killFollower(f:MovingCircle):void
		{
			enemiesKilled++;
			Main.Instance().Puff(f.x, f.y);
			SoundLib.Instance().PlaySound(f.x,f.y,SoundLib.SBlowUp);
			lso.data.kills = enemiesKilled;
			//TO DO: remove LSO flush from there and put it somewhere to the end of the level (flush is SLOW!)
			lso.flush(1024);//1K ought to be enough
			enemycount--;
			updateFrags();
			if (!enemycount)
				NextLevel();
		}
	
		public function killPlayer():void
		{
			removeEventListener(Event.ENTER_FRAME, moveActors);
			var field:TextField = new TextField();
			field.x = stage.stageWidth / 2;
			field.y = stage.stageHeight / 2;
			field.autoSize = TextFieldAutoSize.CENTER;
			field.background = false;
			field.border = false;
			field.textColor = 0xFFFFFF;
			field.text = "GAME OVER!";
			var formatter:TextFormat = new TextFormat();
			formatter.bold = true;
			formatter.size = 46;
			formatter.align = "center";
			field.setTextFormat(formatter);
			addChild(field);
		}
		
		public function playerWin():void
		{
			removeEventListener(Event.ENTER_FRAME, moveActors);
			var field:TextField = new TextField();
			field.x = stage.stageWidth / 2;
			field.y = stage.stageHeight / 2;
			field.autoSize = TextFieldAutoSize.CENTER;
			field.background = false;
			field.border = false;
			field.textColor = 0xFFFFFF;
			field.text = "YOU WIN!";
			var formatter:TextFormat = new TextFormat();
			formatter.bold = true;
			formatter.size = 46;
			formatter.align = "center";
			field.setTextFormat(formatter);
			addChild(field);
		}
		public function scrollLevel():void
		{
			//trace("Pl: "+p.x+" "+p.y+" St: "+x+" "+y);
			if ((p.x-Math.abs(container.x) > 3*stage.stageWidth / 4) && (Math.abs(container.x) < container.width-stage.stageWidth))
			{
				container.x = -p.x + 3*stage.stageWidth / 4;// * (1 + p.boost);
			}
			if ((p.x-Math.abs(container.x) <stage.stageWidth/4) && (container.x<0))
			{
				container.x = -p.x + stage.stageWidth/4;// * (1 + p.boost);
			}
			if (p.y-Math.abs(container.y) > (3 * stage.stageHeight / 4) && (Math.abs(container.y)<container.height-stage.stageHeight))
			{
				container.y = -p.y + 3*stage.stageHeight/ 4;// * (1 + p.boost);
			}
			if ((p.y-Math.abs(container.y) < stage.stageHeight / 4) && (container.y<0))
			{
				container.y = -p.y + stage.stageHeight/4;// * (1 + p.boost);
			}
		}
		public function NextLevel():void
		{
			map.levelnum++;
			if (map.levelnum == NUMBEROFLEVELS)
			{
				playerWin();
				return;
			}
			removeChild(container);
			p.destroy();
			if (map.levelnum == 3)
				map.levelnum = 0;
			LoadLevel();
		}
	}
}